#include "OgreGLES2HardwareBufferManager.h"
#include "OgreGLES2HardwareVertexBuffer.h"
#include "OgreRoot.h"
#include "OgreGLES2RenderSystem.h"

namespace Ogre
{
	GLES2HardwareVertexBuffer::GLES2HardwareVertexBuffer(HardwareBufferManagerBase* mgr,
														 size_t vertexSize,
														 size_t numVertices,
														 HardwareBuffer::Usage usage,
														 bool useShadowBuffer)
		: HardwareVertexBuffer(mgr, vertexSize, numVertices, usage, false, useShadowBuffer)
	{
		if (!mUseShadowBuffer)
		{
			OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR,
				"Only supported with shadowBuffer",
				"GLES2HardwareVertexBuffer");
		}

		glGenBuffers(1, &mBufferId);
		GL_CHECK_ERROR;

		if (!mBufferId)
		{
			OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR,
				"Cannot create GL ES vertex buffer",
				"GLES2HardwareVertexBuffer::GLES2HardwareVertexBuffer");
		}

		dynamic_cast<GLESRenderSystem*>(Root::getSingleton().getRenderSystem())->_bindGLBuffer(GL_ARRAY_BUFFER, mBufferId);
		GL_CHECK_ERROR;
		glBufferData(GL_ARRAY_BUFFER, mSizeInBytes, NULL,
			GLES2HardwareBufferManager::getGLUsage(usage));
		GL_CHECK_ERROR;
	}

	GLES2HardwareVertexBuffer::~GLES2HardwareVertexBuffer()
	{
		glDeleteBuffers(1, &mBufferId);
		GL_CHECK_ERROR;
	}

	void* GLES2HardwareVertexBuffer::lockImpl(size_t offset,
											  size_t length,
											  LockOptions options)
	{
		GLenum access = 0;

		if (mIsLocked)
		{
			OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR,
					"Invalid attempt to lock an index buffer that has already been locked",
					"GLES2HardwareVertexBuffer::lock");
		}

		void* retPtr = 0;

		GLES2HardwareBufferManager* glBufManager = static_cast<GLES2HardwareBufferManager*>(HardwareBufferManager::getSingletonPtr());

		if (length < glBufManager->getGLMapBufferThreshold())
		{
			retPtr = glBufManager->allocateScratch((uint32)length);

			if (retPtr)
			{
				mLockedToScratch = true;
				mScratchOffset = offset;
				mScratchSize = length;
				mScratchPtr = retPtr;
				mScratchUploadOnUnlock = (options != HBL_READ_ONLY);

				if (options != HBL_DISCARD)
				{
					readData(offset, length, retPtr);
				}
			}
		}
		else
		{
			OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR,
					"Invalid Buffer lockSize",
					"GLES2HardwareVertexBuffer::lock");
		}

#if GL_OES_mapbuffer
		if (!retPtr)
		{
			dynamic_cast<GLESRenderSystem*>(Root::getSingleton().getRenderSystem())->_bindGLBuffer(GL_ARRAY_BUFFER, mBufferId);
			if (options == HBL_DISCARD)
			{
				glBufferData(GL_ARRAY_BUFFER, mSizeInBytes, NULL,
					GLES2HardwareBufferManager::getGLUsage(mUsage));
			}
			if (mUsage & HBU_WRITE_ONLY)
				access = GL_WRITE_ONLY_OES;

			void* pBuffer = glMapBufferOES(GL_ARRAY_BUFFER, access);

			if (pBuffer == 0)
			{
				OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR,
					"Vertex Buffer: Out of memory", "GLES2HardwareVertexBuffer::lock");
			}

			retPtr = static_cast<void*>(
				static_cast<unsigned char*>(pBuffer) + offset);

			mLockedToScratch = false;
		}
#endif
		mIsLocked = true;
		return retPtr;
	}

	void GLES2HardwareVertexBuffer::unlockImpl(void)
	{
		if (mLockedToScratch)
		{
			if (mScratchUploadOnUnlock)
			{
				writeData(mScratchOffset, mScratchSize, mScratchPtr,
					mScratchOffset == 0 && mScratchSize == getSizeInBytes());
			}

			static_cast<GLES2HardwareBufferManager*>(
				HardwareBufferManager::getSingletonPtr())->deallocateScratch(mScratchPtr);

			mLockedToScratch = false;
		}
		else
		{
#if GL_OES_mapbuffer
			dynamic_cast<GLES2RenderSystem*>(Root::getSingleton().getRenderSystem())->_bindBuffer(GL_ARRAY_BUFFER, mBufferId);

			if (!glUnmapBuffer(GL_ARRAY_BUFFER))
			{
				OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR,
					"Buffer data corrputed, please reload",
					"GLES2HardwareVertexBuffer::unlock");
			}
#else
			OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR,
				"Only locking to scratch is supported",
				"GLES2HardwareVertexBuffer::unlockImpl");
#endif
		}

		mIsLocked = false;
	}

	void GLES2HardwareVertexBuffer::readData(size_t offset, size_t length, void* pDest)
	{
		if (mUseShadowBuffer)
		{
			void *srcData = mpShadowBuffer->lock(offset, length, HBL_READ_ONLY);
			memcpy(pDest, srcData, length);
			mpShadowBuffer->unlock();
		}
		else
		{
			OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR,
					"Read hardware buffer is not supported",
					"GLESHardwareVertexBuffer::readData");
		}
	}

	void GLESHardwareVertexBuffer::writeData(size_t offset,
											size_t length,
											const void* pSource,
											bool discardWholeBuffer)
	{
		dynamic_cast<GLES2RenderSystem*>(Root::getSingleton().getRenderSystem())->_bindGLBuffer(GL_ARRAY_BUFFER, mBufferId);
		GL_CHECK_ERROR;

		if (mUseShadowBuffer)
		{
			void* destData = mpShadowBuffer->lock(offset, length,
				discardWholeBuffer ? HBL_DISCARD : HBL_NORMAL);
			memcpy(destData, pSource, length);
			mpShadowBuffer->unlock();
		}

		if (offset == 0 && length == mSizeInBytes)
		{
			glBufferData(GL_ARRAY_BUFFER, mSizeInBytes, pSource,
				GLES2HardwareBufferManager::getGLUsage(mUsage));
			GL_CHECK_ERROR;
		}
		else
		{
			if (discardWholeBuffer)
			{
				glBufferData(GL_ARRAY_BUFFER, mSizeInBytes, NULL,
					GLES2HardwareBufferManager::getGLUsage(mUsage));
				GL_CHECK_ERROR;
			}

			glBufferSubData(GL_ARRAY_BUFFER, offset, length, pSource);
			GL_CHECK_ERROR;
		}
	}

	void GLES2HardwareVertexBuffer::_updateFromShadow(void)
	{
		if (mUseShadowBuffer && mShadowUpdated && !mSuppressHardwareUpdate)
		{
			const void* srcData = mpShadowBuffer->lock(
				mLockStart,
				mLockSize,
				HBL_READ_ONLY);

			dynamic_cast<GLES2RenderSystem*>(Root::getSingleton().getRenderSystem())->_bindGLBuffer(GL_ARRAY_BUFFER, mBufferId);
			GL_CHECK_ERROR;

			if (mLockStart == 0 && mLockSize == mSizeInBytes)
			{
				glBufferData(GL_ARRAY_BUFFER, mSizeInBytes, srcData,
					GLES2HardwareBufferManager::getGLUsage(mUsage));
				GL_CHECK_ERROR;
			}
			else
			{
				glBufferSubData(GL_ARRAY_BUFFER, mLockStart, mLockSize, srcData);
				GL_CHECK_ERROR;
			}

			mpShadowBuffer->unlock();
			mShadowUpdated = false;
		}
	}
}
